/*
 * SniffEngine.cpp
 *
 *  Created on: Apr 3, 2013
 *      Author: root
 */

#include "SniffEngine.h"
#include "common.h"
#include <map>
#include <string.h>
#include <pcap.h>
#include <sstream>
#include <iostream>
using namespace std;


void startDashSniffer(const char *internalDeviceStr, const char *externalDeviceStr)
{

	int               t;
	fd_set            fd_wait;
	struct timeval    st;
	struct pcap      *hInternal;
	struct pcap      *hExternal;
	char              errbuf[PCAP_ERRBUF_SIZE];

	SnifSTAT snifStatistics;
	memset(&snifStatistics, 0, sizeof(SnifSTAT));//init
	//snifStatistics.slidingWindowSize = slidingWindowSize;

	/*open an internal device for capturing*/
	hInternal = pcap_open_live(internalDeviceStr, BUFSIZ, 1, -1, errbuf);
	if(hInternal == NULL)
	{
		fprintf(stderr, "ERROR can't  open device \"%s\": %s\n", internalDeviceStr, errbuf);
		exit(1);
	}
	/*get the state of non-blocking mode on a capture device*/
	if(pcap_setnonblock(hInternal, 1, errbuf) == 1)
	{
			fprintf(stderr, "ERROR can't set device \"%s\" to non-blocking: %s\n",
					internalDeviceStr,
					errbuf);
			exit(1);
	}
	/*open an external device for capturing*/
	hExternal = pcap_open_live(externalDeviceStr, BUFSIZ, 1, -1, errbuf);
	if(hExternal == NULL)
	{
		fprintf(stderr, "ERROR can't open the  device \"%s\": %s\n", externalDeviceStr, errbuf);
		exit(1);
	}

	/*get the state of non-blocking mode on a capture device*/
	if(pcap_setnonblock(hExternal, 1, errbuf) == 1)
	{
			fprintf(stderr, "ERROR can't  set the device \"%s\" to non-blocking: %s\n", externalDeviceStr,
					errbuf);
			exit(1);
	}

	snifStatistics.hExternal = hExternal;
	snifStatistics.hInternal = hInternal;

	strncpy(snifStatistics.internalDevice, internalDeviceStr, sizeof(snifStatistics.internalDevice));
	strncpy(snifStatistics.externalDevice, externalDeviceStr, sizeof(snifStatistics.externalDevice));
	bool continueSniffing = true;

	while (continueSniffing)
	{
		FD_ZERO(&fd_wait);//to clear a set
		/*get the file descriptor for a live capture for internal and external*/
		FD_SET(pcap_fileno(hInternal), &fd_wait);//add a descriptor to the set
		FD_SET(pcap_fileno(hExternal), &fd_wait);
		st.tv_sec  = 1;
		 /** 1000 = 1 second */
		st.tv_usec = 0;
		/*select()the function wait for a number of file descriptors to change status */
		t = select(FD_SETSIZE, &fd_wait, NULL, NULL, &st);

		switch(t)//will return zero if the timeout expires before anything intresting will happen,-1 on error.
		{
			// Error
			case -1:
			{
				fprintf(stderr, "Exiting the packet dispatching loop\n\n");
				continueSniffing = false;
				break;
			}

			// Timed out
			case  0:
			{
				fprintf(stderr, ".");//if won't receive any packet the we will get "." until we get a new packet
				break;
			}

			// Handle an arriving packet - - - - -- -   - packet lifting?
			default:
			{
				snifStatistics.curPacketDirection = ENUM_INTERNAL_PACKET;
				//DEBUG("Recived internal");
				/*process packet from a live capture (send it to analyze function)*/
				//(Pcap Dispatch - ( , number of packets to handel
				pcap_dispatch(hInternal, 1, handelConnection , (u_char*) &snifStatistics);

				snifStatistics.curPacketDirection = ENUM_EXTERNAL_PACKET;
				/*process packet from a live capture*/
				pcap_dispatch(hExternal, 1, handelConnection , (u_char*) &snifStatistics);
			}
		}

	}

	pcap_close(hInternal);
	pcap_close(hExternal);
	fprintf(stderr, "Exiting\n");
	printStats(&snifStatistics);

}

void handelConnection(u_char *user, const struct pcap_pkthdr *h,const u_char *bytes)
{

	bool ForwardPacket = true;
		SnifSTAT *snifStatistics = (SnifSTAT*) user;
		//HTTPsession *httpSession=NULL;

		if (ENUM_INTERNAL_PACKET == snifStatistics->curPacketDirection)
		{
			++snifStatistics->internalPacketsIn;
		}

		if (ENUM_EXTERNAL_PACKET == snifStatistics->curPacketDirection)
		{
			++snifStatistics->externalPacketsIn;
		}

		TPacketPointers request = {0};
		TPacketPointers *packet=&request;
		fillRequestPacketStruct(&request, bytes, h->len);
		snifStatistics->slidingWindowSize=6400;

		if (ForwardPacket)
				{
					forwardPacket(snifStatistics, bytes, h->len, false);
				}
		//printPacket(&request);

	}


STATE fillRequestPacketStruct(TPacketPointers *toFill, const uint8_t *bytes, u_int32_t len)
{
	memset(toFill, 0, sizeof(TPacketPointers));

	toFill->packetData = (u_int8_t*) bytes;
	toFill->packetLen = len;

	// Simulating a prepared packet from the data plane
	toFill->ethernetHeader = (struct ether_header*) bytes;
	toFill->ethernetHeaderLen = sizeof(struct ether_header);
	if (ntohs(toFill->ethernetHeader->ether_type) == ETHERTYPE_VLAN)
	{
		toFill->ethernetHeaderLen += 4; // Size of the VLAN tag
	}

	toFill->ipHeader = (struct ip*) (bytes + toFill->ethernetHeaderLen);
	toFill->ipHeaderLen = sizeof(struct ip);

	toFill->ipProtocol = toFill->ipHeader->ip_p;
	//we don't care if it is UDP!
	if (toFill->ipProtocol == IPPROTO_TCP)
    {
	    // The TCP header size varies - if there are TCP options, it is longer than sizeof(struct tcphdr)
        toFill->tcpHeader = (struct tcphdr*) ((char*) toFill->ipHeader + toFill->ipHeaderLen);
	    toFill->tcpHeaderLen = 4 * toFill->tcpHeader->doff; // a 4 bits field doesn't have an endian to think about...
	    if (0 == toFill->tcpHeaderLen)
	    {
	        toFill->tcpHeaderLen = sizeof(struct tcphdr);
	    }
	    /**
	     * This is the payload! here we need to check the FLV header if exist!
	     * */
        toFill->layer5Data = (u_int8_t*) ((char*) toFill->tcpHeader) + toFill->tcpHeaderLen;
        toFill->layer5DataLen = len - (toFill->layer5Data - bytes);

    } else
    {
        return S_ERROR;
    }

	return OK;
}


void printStats(SnifSTAT *snifStatistics)
{
	printf("Internal NIC (%s):\t\tInbound packets:\t%lld\t\tOutbound packets:\t%lld\n",
			snifStatistics->internalDevice, snifStatistics->internalPacketsIn,
			snifStatistics->internalPacketsOut);
	printf("External NIC (%s):\t\tInbound packets:\t%lld\t\tOutbound packets:\t%lld\n",
			snifStatistics->externalDevice, snifStatistics->externalPacketsIn,
			snifStatistics->externalPacketsOut);
}

STATE sendPacket(SnifSTAT *snifStatistics, const u_char *bytes, u_int32_t len, bool forceSend, bool isResponse)
{
	STATE retVal = OK;


		switch (snifStatistics->curPacketDirection)
		{
			case ENUM_INTERNAL_PACKET:
			{
				if (isResponse)
				{
					pcap_inject(snifStatistics->hInternal, bytes, len);/*Transmit a packet*/
					++snifStatistics->internalPacketsOut;
				}
				// forward
				else
				{
					pcap_inject(snifStatistics->hExternal, bytes, len);/*Transmit a packet*/
					++snifStatistics->externalPacketsOut;
				}
				break;
			}
			case ENUM_EXTERNAL_PACKET:
			{
				if (isResponse)
				{
					pcap_inject(snifStatistics->hExternal, bytes, len);
					++snifStatistics->externalPacketsOut;
				}
				else // Forward
				{
					pcap_inject(snifStatistics->hInternal, bytes, len);
					++snifStatistics->internalPacketsOut;
				}
				break;
			}
		}

	return retVal;
}


STATE respondPacket(SnifSTAT *snifStatistics, const u_char *bytes, u_int32_t len, bool forceSend)
{
	return sendPacket(snifStatistics, bytes, len, forceSend, true);
}

STATE forwardPacket(SnifSTAT *snifStatistics, const u_char *bytes, u_int32_t len, bool forceSend)
{
	return sendPacket(snifStatistics, bytes, len, forceSend, false);
}


void printPacket(TPacketPointers *packet)
{
	printf("Ethernet (%d):\n", packet->ethernetHeaderLen);
	printf("IP (%d):\n", packet->ipHeaderLen);

	if (packet->ipProtocol == IPPROTO_TCP && NULL != packet->tcpHeader)
    {
        printf("TCP (%d):\n", packet->tcpHeaderLen);

    }

	if (NULL != packet->layer5Data)
	{
		printf("L5 (%d):\n", packet->layer5DataLen);

	}
}
